﻿
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Linq;

public class LoadedAssetBundle
{
    public AssetBundle AssetBundle;
    public int ReferencedCount;

    public LoadedAssetBundle(AssetBundle assetBundle)
    {
        AssetBundle = assetBundle;
        ReferencedCount = 1;
    }
}

public class AssetBundleManager : Singleton<AssetBundleManager>
{
    private AssetBundleManifest mManifest = null;

    //当前存在的资源
    Dictionary<string, LoadedAssetBundle> mLoadedAssetBundleMap = new Dictionary<string, LoadedAssetBundle>();
    //依赖选项
    Dictionary<string, string[]> mDependencieMap = new Dictionary<string, string[]>();
    //避免依赖选项的重复加载
    Dictionary<string, WWW> mLoadingWWWMap = new Dictionary<string, WWW>();

    public void Initialize()
    {
        LoadManifest();
    }

    private AssetBundleManifest GetAssetBundleManifest()
    {
        if (mManifest == null)
        {
            Initialize();
        }
        return mManifest;
    }

    public LoadedAssetBundle GetLoadedAssetBundle(string assetBundleName)
    {
        LoadedAssetBundle bundle = null;
        mLoadedAssetBundleMap.TryGetValue(assetBundleName, out bundle);
        if (bundle == null)
        {
            return null;
        }

        string[] dependencies = null;
        if (!mDependencieMap.TryGetValue(assetBundleName, out dependencies))
        {
            return bundle;
        }

        for (int i = 0; i < dependencies.Length; i++)
        {
            string dependency = dependencies[i];
            LoadedAssetBundle dependentBundle;
            mLoadedAssetBundleMap.TryGetValue(dependency, out dependentBundle);
            if (dependentBundle == null)
            {
                return null;
            }
        }

        return bundle;
    }

    /// <summary>
    /// 同步加载Manitest
    /// </summary>
    private void LoadManifest()
    {
        string manifestName = AssetBundleConfig.GetManitestName();
        LoadAssetBundle(manifestName, false);

        if (mLoadedAssetBundleMap.ContainsKey(manifestName))
        {
            mManifest = mLoadedAssetBundleMap[manifestName].AssetBundle.LoadAsset("AssetBundleManifest") as AssetBundleManifest;
        }

        UnloadAssetBundle(manifestName);
    }

    /// <summary>
    /// 异步加载资源
    /// </summary>
    /// <param name="assetBundleName"></param>
    /// <param name="needCheckDependencies">是否检查依赖项</param>
    /// <returns></returns>
    protected IEnumerator LoadAssetBundleAsync(string assetBundleName, bool needCheckDependencies = true)
    {
        if (needCheckDependencies)
        {
            yield return StartCoroutine(LoadDependenciesAsync(assetBundleName));
        }

        yield return StartCoroutine(LoadAssetBundleInternalAsync(assetBundleName));
    }

    /// <summary>
    /// 异步加载依赖项
    /// </summary>
    /// <param name="assetBundleName"></param>
    /// <returns></returns>
    protected IEnumerator LoadDependenciesAsync(string assetBundleName)
    {
        if (GetAssetBundleManifest() == null)
        {
            Debug.LogError("Please call OnInitialize() to initialize GetAssetBundleManifest");
            yield break;
        }

        string[] dependencies = GetAssetBundleManifest().GetAllDependencies(assetBundleName);

        if (dependencies.Length == 0)
        {
            yield break;
        }

        if (!mDependencieMap.ContainsKey(assetBundleName))
        {
            mDependencieMap.Add(assetBundleName, dependencies);
        }

        for (int i = 0; i < dependencies.Length; i++)
        {
            yield return StartCoroutine(LoadAssetBundleInternalAsync(dependencies[i]));
        }
    }

    /// <summary>
    /// 异步加载过程
    /// </summary>
    /// <param name="assetBundleName"></param>
    /// <returns></returns>
    protected IEnumerator LoadAssetBundleInternalAsync(string assetBundleName)
    {
        LoadedAssetBundle bundle = null;
        mLoadedAssetBundleMap.TryGetValue(assetBundleName, out bundle);
        if (bundle != null)
        {
            bundle.ReferencedCount++;
            yield return null;
        }

        WWW www = null;
        if (!mLoadingWWWMap.ContainsKey(assetBundleName))
        {
            string url = AssetBundleConfig.GetAssetsBasePath() + assetBundleName;
            www = WWW.LoadFromCacheOrDownload(url, GetAssetBundleManifest().GetAssetBundleHash(assetBundleName), 0);
            mLoadingWWWMap.Add(assetBundleName, www);
        }

        www = mLoadingWWWMap[assetBundleName];
        yield return www;
        mLoadedAssetBundleMap.TryGetValue(assetBundleName, out bundle);
        if (bundle != null)
        {
            bundle.ReferencedCount++;
            yield break;
        }

        if (www.error != null)
        {
            Debug.LogError("Load AssetBundle @[" + assetBundleName + "] ERROR! [" + www.error + "]");
            yield break;
        }

        if (www.isDone)
        {
            bundle = new LoadedAssetBundle(www.assetBundle);
            mLoadedAssetBundleMap.Add(assetBundleName, bundle);
            mLoadingWWWMap.Remove(assetBundleName);
            www.Dispose();
        }
    }

    /// <summary>
    /// 同步加载资源
    /// </summary>
    /// <param name="assetBundleName"></param>
    /// <param name="needCheckDependencies">是否检测依赖项</param>
    protected void LoadAssetBundle(string assetBundleName, bool needCheckDependencies = true)
    {
        if (needCheckDependencies)
        {
            LoadDependencies(assetBundleName);
        }

        LoadAssetBundleInternal(assetBundleName, needCheckDependencies);
    }

    /// <summary>
    /// 同步加载依赖项
    /// </summary>
    /// <param name="assetBundleName"></param>
    protected void LoadDependencies(string assetBundleName)
    {
        if (GetAssetBundleManifest() == null)
        {
            Debug.LogError("Please call OnInitialize() to initialize GetAssetBundleManifest");
            return;
        }

        string[] dependencies = GetAssetBundleManifest().GetAllDependencies(assetBundleName);
        if (dependencies.Length == 0)
        {
            return;
        }

        if (!mDependencieMap.ContainsKey(assetBundleName))
        {
            mDependencieMap.Add(assetBundleName, dependencies);
        }

        for (int i = 0; i < dependencies.Length; i++)
        {
            LoadAssetBundleInternal(dependencies[i], false);
        }
    }

    /// <summary>
    /// 同步加载资源过程
    /// </summary>
    /// <param name="assetBundleName"></param>
    /// <param name="isLoadingAssetBundleManifest"></param>
    protected void LoadAssetBundleInternal(string assetBundleName, bool isLoadingAssetBundleManifest)
    {
        LoadedAssetBundle bundle = null;
        mLoadedAssetBundleMap.TryGetValue(assetBundleName, out bundle);
        if (bundle != null)
        {
            bundle.ReferencedCount++;
            return;
        }

        string url = AssetBundleConfig.GetAssetsBasePath(true) + assetBundleName;
        AssetBundle assetBundle = null;

        try
        {
            assetBundle = AssetBundle.LoadFromFile(url);
        }
        catch (Exception e)
        {
            Debug.LogError("Load AssetBundle @ [" + url + "], Error [" + e + "]");
        }
        catch
        {
            Debug.LogError("Load AssetBundle @ [" + url + "], native exception!!");
        }

        mLoadedAssetBundleMap.TryGetValue(assetBundleName, out bundle);
        if (bundle != null)
        {
            bundle.ReferencedCount++;
            return;
        }

        if (assetBundle != null)
        {
            bundle = new LoadedAssetBundle(assetBundle);
            mLoadedAssetBundleMap.Add(assetBundleName, bundle);
        }
    }

    /// <summary>
    /// 卸载
    /// </summary>
    /// <param name="assetBundleName"></param>
    /// <param name="unloadDependencies"></param>
    public void UnloadAssetBundle(string assetBundleName, bool unloadDependencies = true)
    {
        UnloadAssetBundleInternal(assetBundleName);
        if (unloadDependencies)
        {
            UnloadDependencies(assetBundleName);
        }
    }

    protected void UnloadDependencies(string assetBundleName)
    {
        string[] dependencies = null;
        if (!mDependencieMap.TryGetValue(assetBundleName, out dependencies))
        {
            return;
        }

        for (int i = 0; i < dependencies.Length; i++)
        {
            string dependency = dependencies[i];
            UnloadAssetBundleInternal(dependency);
        }

        mDependencieMap.Remove(assetBundleName);
    }

    protected void UnloadAssetBundleInternal(string assetBundleName)
    {
        LoadedAssetBundle bundle = GetLoadedAssetBundle(assetBundleName);
        if (bundle == null)
        {
            return;
        }

        if (--bundle.ReferencedCount == 0)
        {
            if (bundle.AssetBundle != null)
            {
                bundle.AssetBundle.Unload(false);
            }

            mLoadedAssetBundleMap.Remove(assetBundleName);
        }
    }

    /// <summary>
    /// 同步加载资源,直接返回物体
    /// </summary>
    /// <typeparam name="T">类型</typeparam>
    /// <param name="assetPath">  资源知实际路径，比如:"resources/model/cube" </param>
    /// <param name="unloadDependencies">是否卸载依赖项,默认释放</param>
    /// <returns>返回创建的物体</returns>
    public T LoadObjectFromAssetBundle<T>(string assetPath, bool unloadDependencies = true) where T : UnityEngine.Object
    {
        T res = null;
        
        string bundleName = AssetBundleUtil.GetBundleName(assetPath);
        string assetName = assetPath.Substring(assetPath.LastIndexOf("/") + 1);

        LoadAssetBundle(bundleName);

        if (mLoadedAssetBundleMap.ContainsKey(bundleName) && mLoadedAssetBundleMap[bundleName].AssetBundle != null)
        {
            res = (T)mLoadedAssetBundleMap[bundleName].AssetBundle.LoadAsset(assetName);
        }

#if UNITY_EDITOR
        if (res != null && typeof(T) == typeof(GameObject))
        {
            Renderer[] Renders = (res as GameObject).GetComponentsInChildren<Renderer>();
            for (int i = 0; i < Renders.Length; i++)
            {
                Material[] materials = Renders[i].sharedMaterials;
                for (int j = 0; j < materials.Length; j++)
                {
                    materials[j].shader = Shader.Find(materials[j].shader.name);
                }
            }
        }
#endif
        UnloadAssetBundle(bundleName, unloadDependencies);
        return res;
    }

    /// <summary>
    /// 异步加载接口
    /// </summary>
    /// <param name="assetPath"></param>
    /// <param name="callback"></param>
    /// <param name="unloadDependencies"></param>
    public void LoadObjectFromAssetBundleAsync(string assetPath, Action<GameObject> callback, bool unloadDependencies = true)
    {
        StartCoroutine(LoadObjectFromAssetBundleAsyncInternal(assetPath, callback, unloadDependencies));
    }

    /// <summary>
    /// 内部异步加载接口
    /// </summary>
    /// <param name="assetPath"></param>
    /// <param name="callback"></param>
    /// <param name="unloadDependencies"></param>
    /// <returns></returns>
    /// NOTE:无实例化
    private IEnumerator LoadObjectFromAssetBundleAsyncInternal(string assetPath , Action<GameObject> callback, bool unloadDependencies = true)
    {
        string bundleName = AssetBundleUtil.GetBundleName(assetPath);
        string assetName = assetPath.Substring(assetPath.LastIndexOf("/") + 1);

        yield return StartCoroutine(LoadAssetBundleAsync(bundleName));

        if (callback != null)
        {
            GameObject obj = null;
            if (mLoadedAssetBundleMap.ContainsKey(bundleName) && mLoadedAssetBundleMap[bundleName].AssetBundle != null)
            {
                obj = mLoadedAssetBundleMap[bundleName].AssetBundle.LoadAsset(assetName) as GameObject;
            }
            callback(obj);
        }
        UnloadAssetBundle(bundleName, unloadDependencies);
    }

    [ContextMenu("UnloadDependencies")]
    public void UnloadDependencies()
    {
        List<string> keys = mDependencieMap.Keys.ToList();
        for (int i = 0; i < keys.Count; i++)
        {
            UnloadDependencies(keys[i]);
        }
    }

    public List<AssetBundle> GetAllLoadedAssetBundle()
    {
        List<AssetBundle> m_AB = new List<AssetBundle>();
        foreach (KeyValuePair<string, LoadedAssetBundle> mLoadedAssetBundle in mLoadedAssetBundleMap)
        {
            if (mLoadedAssetBundle.Value.AssetBundle != null)
            {
                m_AB.Add(mLoadedAssetBundle.Value.AssetBundle);
            }
        }
        return m_AB;
    }
}
